Ruby on Rails concepts explained with real-world use cases

Neel Vikmani
codeburst
Published in
9 min readDec 19, 2019

--

Five concepts you must know if you want to build better production applications with Ruby on Rails

Hi there, I have written this article to highlight the basic concepts of Ruby and its amazing framework-Ruby on Rails(RoR). This article will help you in using RoR for your application more efficiently. I have been working on Ruby, more specifically its framework Ruby on Rails(RoR) along with MySQL for the last 7 months, and I have written down everything that I had learned and implemented in this amazing Ruby framework.

Frankly speaking, before starting with Ruby and Ruby on Rails, I had used C#, Java, Javascript(NodeJS) and PHP(Laravel) majority of the times for making different applications. When I switched to Ruby on Rails, I was amazed to see how software development can be so amazing and easier using this framework. The biggest competitive advantage of using Ruby on Rails over other frameworks is the speed of product development. On top of this, it has lots of supporting libraries, which will help you even more in your development journey. It is known for its easy syntax and rapid programming so that you can focus on more important aspects like system designs, integrations and other engineering principles like testability and maintainability.

So now, since I am already done with the honors of Ruby, let's start with the actual concepts.

First things first, I will start with a very common use case, upon which all further concepts will be explained. Let's consider backend of a very basic product catalog service in e-commerce, which deals with product creation, updation, and exposing product details to the outside world, or to other services within organizations via APIs or any message passing mechanisms.

Let’s create a new rails project using the command:

rails new catalog

Note: For demonstration purpose, I will be using MySQL database and ActiveRecord ORM for interacting with the database.

So let’s get started…

1. MVC (Model-View-Controller)

This is the very basic and most common design pattern that many frameworks support, including Laravel, Spring MVC, Ruby on Rails, etc.

Putting it in the simplest way, MVC is a software design pattern that divides the application into three interconnected elements, namely Model, View, and Controller. The basic idea is that each application consists of these three elements, where Model holds the data, View makes the application look nice with clean layouts and consistent data display, and the controller controls the flow between the Model and View, i.e. how the data flows through and out of the application. MVC pattern allows having maintainable and segregated code, which can work wonderous in making further changes as the application scales.

For example: Let’s take a use case where the user wants to get product information when they click on a particular product, or technically speaking, when they enter index pages like <yourdomain>/products/ and clicks on a product from listing, they are directed to PDP(Product Details Page) of that product.

Now you may think it as simple as to listen for those types of GET requests and when the request comes, just hit the product table in the database and get the results and show it on the web page. Well, the approach is straightforward and simple, but with MVC pattern, you can make these requests handling more maintainable and clean.

With rails installed in your system, just navigate to your file system using terminal and type —

rails generate model Product name:string

This will create a model named product.rb in the app/models folder and a migration file in db/migrate folder.

Model file (product.rb)


class Product < ApplicationRecord
end

Migration File (<timestamp>_create_products.rb)

class CreateProducts < ActiveRecord::Migration[5.2]
def change
create_table :products do |t|
t.string :name
t.timestamps
end
end
end

You need to run this migration to be able to create the product table in the database. You can also add some more fields to this file if you need some additional attributes in the product table. After required changes are done, run migrations using command ‘rake db:migrate’. This will create the table in the database.

Since you are all set with the tables and model in hand, let us create controller. Go to terminal and -

rails generate controller Product

This will create a product_controller.rb file inside ‘app/controllers’ folder with the following content:

 
class ProductController < ApplicationController
end

It will also create an empty app/views/product folder, which you can use for defining views for our application. You can also use this file along with any routing mechanisms for developing full-stack web applications.

So instead of maintaining your code in a single file or making custom secondary files and handling processing between each of them, this MVC architecture lays down a common and sleek code design pattern that you need to follow for making your application code more maintainable.

One of the principles of Ruby on Rails is ‘Fat Model, Skinny Controller’, which means make the controller as simple and atomic as possible and provide all transformation and processing logic in the model. I will be using this same principle in easiest way in next few concepts.

2. Trailblazers

This is a concept that has been used widely across many organizations for designing and implementing business logic layers into comprehensive and maintainable code.

As their website quotes-

“Trailblazer’s file structure organizes by CONCEPT, and then by technology.”

They seriously mean it.

The approach of writing, dividing and structuring implementation code into concepts and actually practicing modularity at the code level is the best thing for which you will thank yourself once the production application grows big time.

Use case- Let’s say you have a product availability logic which determines whether any product is available in the inventory at a specified city or not. Let us assume that availability logic requires read access to 3 to 4 tables and also requires some Redis calls to fetch related data, and at the end requires processing at the application end before giving the actual availability. Now you want to do all these things every time someone hits Catalog interface services or APIs. So, for any requested product in any city, the catalog must provide actual availability precisely. Implementing all of this at Model Level or Controller Level alone is a bad practice as the logic may talk to several models and also it may scale in the future. This is where the Trailblazer concept can come into the picture.

A sample trailblazer operation file for calculating availability will look like-

class ProductAvailability < Trailblazer::Operation
step :read_params
step :fetch_product_and_location_details
step :calculate_availability
step :persist_and_build_output
def read_params(options)
# logic to read the product and location id params passed
#if params not passed, return false (this will exit the concept and return to the caller)
end
def fetch_product_and_location_details(options)
# hit db/cache to fetch location and product details
end
def calculate_availability(options)
# logic to calculate availability for a product-location
end
def persist_and_build_output(options)
# store the availability calculated and return the response
end
end

As specified in the first function, returning false from any of the ‘step’ stops the current flow of concept and returns to the caller with the values added to the concept variable “option” till that time.

“options” is the hash parameter which is common to the whole concept, i.e. you can assign product details to options[:product] in the second function and access it in the third function, you can assign final response to options[:result] and return back, and refer to this result from caller variable.

Apart from operations, there are other related functions that this awesome framework provides. You can explore them too if you wish to, the link is provided in bonus section.

3. Active Jobs

This is a framework where you can declare async jobs and run them on any queuing services. Jobs can be as simple as cron jobs, daily cleanup jobs, sending events to a couple of services, running some calculations or processing some update event of a record in the model. Active jobs run parallelly and in a separate thread than the current application thread.

Rails provides this Active Job framework where you can create a job, enqueue the jobs on certain events, specify queue where the job should get enqueued, execute it, and many more functions.

Use case- Let’s say that the catalog service gets the product data from another microservice through a queue, processes it and persists it in its own database. Some attributes of the product might also influence the availability of that product(like if a product is discontinued or banned), so you need to call the availability concept that we saw in the earlier example, to recalculate the availability for that product, and persist it back in database. This should be done parallelly so that the current application thread is not strained with additional overhead.

Create a new file — app/jobs/recalculate_availability_job.rb, with job name as “RecalculateAvailabilityJob”, pass product_id parameter to perform function and call the availability concept which we saw in the last use case inside this function.

class RecalculateAvailabilityJob < ApplicationJob
def perform(id)
ProductAvailability.call product_id: id
end
end

In your Product model, use an after-commit to start a job when the discontinued attribute is changed.


class Product < ApplicationRecord
after_commit :recalculate_availability
def recalculate_availability
RecalculateAvailabilityJob.perform_later id \
if discontinued_previously_changed?
end
end

Here, perform_later is the keyword that enqueues the job to be performed as soon as the queue gets empty. So, whenever attribute ‘discontinued’ of any product is changed, this job gets enqueued which calls the availability concept for the same product whose attribute was changed.

4. Mailers

As the name suggests, this script is used to send mails.

Use case: You want to notify certain people about a product whenever it is banned.

Just create ‘banned_product_mailer.rb’ in app/mailers/ folder.

class BannedProductMailer < ApplicationMailer
def send_mail(name:, email_id:)
attachments['banned_products.txt'] = { mime_type: 'text/plain', content: name.join("\n") }
mail(to: email_id, subject: "Banned Products Alert: #{ENV['RAILS_ENV']}") do |format|
body = "PFA #{result.count} banned/unbanned products"
format.text { render plain: body }
end
end

In your Product model, use an after-commit to send an email when the banned attribute of the product model is made true.

class Product < ApplicationRecord
after_commit :send_mail_on_banned
def send_mail_on_banned
mail_ids = JSON.parse(ENV['BANNED_PRODUCT_MAIL_IDS'])
BannedProductMailer.send_mail(name: name, email_id:) \
if banned_previously_changed? && banned_previous_change[1]
end
end

5. Rake Tasks

These are the scripts that you can write once and deploy it, and then execute via console whenever you need to run it. These tasks are more of one time tasks that are written for a specific purpose like to perform a data cleanup, update some data, perform indexing of all the records, etc. Since the developer doesn’t have access to manually update the production data, these kind of tasks are very useful if there any cleanups, small data anomaly fixes or some ad-hoc tasks that need to be run

For example, a month ago, I had developed a rake task to update any column value of any table. This had been used several times by our team for updating values for some ad-hoc requirements as well as for quick short term data anomalies. Similarly, you can write rake tasks for your own use case to provide solutions for some of the problems.

Hope you liked the read. Now, since you have been reading for quite a time and had reached till here, there is something more for you.

Bonus:

Now that you already have read this whole article, here is a bit more reading about Ruby in-built functions and objects that I have found most useful and have used extensively.

1. map, split, join, flatten, etc…

2. procs and lambdas

3. permutation

4. combination

There are many more functions, objects, block patterns in Ruby, which you can explore and implement in your applications, but the above four links are the ones which I, personally have found most useful.

Also, as promised, below is the link of trailblazer-

If you find many more informative and cool stuffs about Ruby on Rails that can help others, please feel free to share the detailed explanation in comments.

In case if you need any help or want to discuss something, feel free to contact me.

You can connect with me on Medium, Github and Linkedin.

Thank you for reading this article. If you find it informative, please share it with your friends, collegues and connections, so even they can benefit with this article.

--

--